/*
 * libCache.c
 *
 *  Created on: 04/12/2011
 *      Author: utn_so
 */
#include <fcntl.h>
#include <unistd.h>

#include <stdint.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include "tiposCache.h"

extern int8_t imprimirDebug;

extern int32_t TAMANIO_CACHE;
extern t_nodoCache* pCacheFte;
extern pthread_mutex_t mutexUsandoCache;

void deUint32_tAChar(uint32_t numero, char* buffer) {
	uint32_t divisor = 1000000000;
	uint8_t k = 1;
	while (div(div(numero, divisor * 10).rem, divisor).quot == 0 && divisor != 1) {
		divisor = divisor / 10;
		k++;
	}

	uint8_t i;
	for (i = 0; i < 11 - k; i++) {
		buffer[i] = div(div(numero, divisor * 10).rem, divisor).quot + 48;
		divisor = divisor / 10;
	}
}

int8_t apilarCache(char* path, t_lineaCache* lineasCache) {

	t_nodoCache* aux = (t_nodoCache*) malloc(sizeof(t_nodoCache));
	aux->path = (char*) malloc(strlen(path) + 1);
	memset(aux->path, '\0', strlen(path) + 1);
	aux->cantFileDescriptor = 1; //AL APILAR UN NUEVO ARCHIVO ES POR HABERLO ABIERTO
	memcpy(aux->path, path, strlen(path));
	uint16_t cantLineasCache = TAMANIO_CACHE * 1024 / (sizeof(t_lineaCache) - sizeof(uint64_t) - sizeof(time_t));
	aux->lineasCache = (t_lineaCache*) malloc(cantLineasCache * sizeof(t_lineaCache));
	uint16_t i;

	for (i = 0; i < cantLineasCache; i++) {
		(aux->lineasCache)[i] = lineasCache[i];
	}
	aux->sig = pCacheFte;
	pCacheFte = aux;
	return 0;
}

int8_t desapilarCache(t_lineaCache** lineasCache) {
	/*nodoLongFileName *aux;
	 aux = *pLongFileNameFte;
	 *pLongFileNameFte = (*pLongFileNameFte)->sig;
	 *longFileName = aux->longFileName; //COMPROBAR SI ESTA BIEN
	 free(aux);*/

	t_nodoCache* aux;
	aux = pCacheFte;
	pCacheFte = pCacheFte->sig;
	uint16_t cantLineasCache = TAMANIO_CACHE * 1024 / (sizeof(t_lineaCache) - sizeof(uint64_t) - sizeof(time_t));
	(*lineasCache) = (t_lineaCache*) malloc(cantLineasCache * sizeof(t_lineaCache));
	uint16_t i;
	for (i = 0; i < cantLineasCache; i++) {
		(*lineasCache)[i] = (aux->lineasCache)[i];
	}
	free(aux->lineasCache);
	free(aux);
	return 0;
}

void crearInformeCache(int32_t signal) {
	//FILE* fp = fopen("/home/utn_so/Escritorio/cache_dump.txt", "a");
	int32_t fp = open("/home/utn_so/Escritorio/cache_dump.txt", O_WRONLY | O_CREAT, 0600);
	if (fp != -1) {
		lseek(fp, 0, SEEK_END);
		char buffer[TAMANIO_BLOQUE];

		memset(buffer, '\0', TAMANIO_BLOQUE);
		strcpy(buffer, "============================================================\n");
		printf("%s", buffer);
		write(fp, (void*) buffer, strlen(buffer));

		memset(buffer, '\0', TAMANIO_BLOQUE);
		strcpy(buffer, "                     INICIO DE INFORME                     \n");
		printf("%s", buffer);
		write(fp, (void*) buffer, strlen(buffer));

		memset(buffer, '\0', TAMANIO_BLOQUE);
		strcpy(buffer, "============================================================\n");
		printf("%s", buffer);
		write(fp, (void*) buffer, strlen(buffer));

		memset(buffer, '\0', TAMANIO_BLOQUE);
		strcpy(buffer, "\n");
		printf("%s", buffer);
		write(fp, (void*) buffer, strlen(buffer));

		pthread_mutex_lock(&mutexUsandoCache);
		t_nodoCache* aux = pCacheFte;
		while (aux != NULL) {
			memset(buffer, '\0', TAMANIO_BLOQUE);
			strcpy(buffer, "------------------------------------------------------------\n");
			printf("%s", buffer);
			write(fp, (void*) buffer, strlen(buffer));

			memset(buffer, '\0', TAMANIO_BLOQUE);
			strcpy(buffer, "Cache correspondiente al archivo: ");
			strcpy(buffer + 34, aux->path);
			*(buffer + strlen(buffer)) = '\n';
			printf("%s", buffer);
			write(fp, (void*) buffer, strlen(buffer));

			memset(buffer, '\0', TAMANIO_BLOQUE);
			time_t tiempo;
			struct tm* tiempoLocal;
			tiempo = time(NULL);
			tiempoLocal = localtime(&tiempo);
			strcpy(buffer, "Timestamp: ");
			strftime(buffer + 11, 80, "%Y.%m.%d %H:%M:%S\n", tiempoLocal);
			printf("%s", buffer);
			write(fp, (void*) buffer, strlen(buffer));

			memset(buffer, '\0', TAMANIO_BLOQUE);
			strcpy(buffer, "Tamanio de Bloque de Cache: 4096B\n");
			printf("%s", buffer);
			write(fp, (void*) buffer, strlen(buffer));

			memset(buffer, '\0', TAMANIO_BLOQUE);
			uint32_t i;
			uint16_t cantLineasCache = TAMANIO_CACHE * 1024 / (sizeof(t_lineaCache) - sizeof(uint64_t) - sizeof(time_t));
			for (i = 0; i < cantLineasCache && ((aux->lineasCache)[i]).numBloque != 0; i++)
				;
			cantLineasCache = i;
			strcpy(buffer, "Cantidad de Bloques de Cache: ");
			deUint32_tAChar(i, buffer + 30);
			*(buffer + strlen(buffer)) = '\n';
			printf("%s", buffer);
			write(fp, (void*) buffer, strlen(buffer));

			for (i = 0; i < cantLineasCache; i++) {
				memset(buffer, '\0', TAMANIO_BLOQUE);
				strcpy(buffer, "Contenido de Bloque de Cache ");
				deUint32_tAChar(i, buffer + 29);
				*(buffer + strlen(buffer)) = '\n';
				printf("%s", buffer);
				write(fp, (void*) buffer, strlen(buffer));

				memset(buffer, '\0', TAMANIO_BLOQUE);
				uint16_t j;
				/*char* numBloque = &(aux->lineasCache[i].numBloque);
				 for (j = 0; j < 8; j++) {
				 buffer[j] = numBloque[j];
				 printf("%c", buffer[j]);
				 write(fp, (void*)buffer, strlen(buffer));
				 }*/
				for (j = 0; j < TAMANIO_BLOQUE; j++) {
					if ((((aux->lineasCache)[i]).contenidoBloque)[j] != '\0') {
						printf("%c", (((aux->lineasCache)[i]).contenidoBloque)[j]);
						write(fp, (void*) &(((aux->lineasCache)[i]).contenidoBloque)[j], 1);
					}
				}

				memset(buffer, '\0', TAMANIO_BLOQUE);
				buffer[0] = '\n';
				printf("%s", buffer);
				write(fp, (void*) buffer, 1);
			}

			memset(buffer, '\0', TAMANIO_BLOQUE);
			strcpy(buffer, "\n\n");
			printf("%s", buffer);
			write(fp, (void*) buffer, strlen(buffer));

			aux = aux->sig;
		}
		pthread_mutex_unlock(&mutexUsandoCache);

		memset(buffer, '\0', TAMANIO_BLOQUE);
		strcpy(buffer, "============================================================\n");
		printf("%s", buffer);
		write(fp, (void*) buffer, strlen(buffer));

		memset(buffer, '\0', TAMANIO_BLOQUE);
		strcpy(buffer, "                       FIN DE INFORME                       \n");
		printf("%s", buffer);
		write(fp, (void*) buffer, strlen(buffer));

		memset(buffer, '\0', TAMANIO_BLOQUE);
		strcpy(buffer, "============================================================\n");
		printf("%s", buffer);
		write(fp, (void*) buffer, strlen(buffer));

		close(fp);
	} else {
		//ERROR AL ABRIR EL ARCHIVO
		printf("Error al abrir el archivo cache_dump.txt.\n");
	}

}

t_nodoCache* buscarPathEnListaCache(const char* path) {
	t_nodoCache* aux = pCacheFte;
	int8_t encontre = 0;
	while (aux != NULL && !encontre) {
		if (strcmp(aux->path, path) == 0) {
			encontre = 1;
			return aux;
		}
		aux = aux->sig;
	}
	return NULL;
}

int8_t buscarBloqueEnCache(char* path, int64_t numBloque, char** contenido) {
	if (imprimirDebug) {
		/**/printf("Busco en la cache.\n");
	}
	t_nodoCache* aux = pCacheFte;
	int8_t encontre = 0;
	uint16_t cantLineasCache = TAMANIO_CACHE * 1024 / (sizeof(t_lineaCache) - sizeof(uint64_t) - sizeof(time_t));

	if (cantLineasCache > 0) {
		//PUEDE HABER POR LO MENOS UN BLOQUE EN LA CACHE
		while (aux != NULL && !encontre) {
			if (imprimirDebug) {
				/**/printf("Encontre el path [%s].\n", aux->path);
			}
			if (strcmp(path, aux->path) == 0) {
				if (imprimirDebug) {
					/**/printf("Encontre el archivo [%s].\n", path);
				}
				uint16_t i;
				for (i = 0; (i < cantLineasCache) && (aux->lineasCache[i].numBloque != numBloque); i++)
					;
				if ((i < cantLineasCache) && (aux->lineasCache[i].numBloque == numBloque)) {
					if (imprimirDebug) {
						/**/printf("El bloque [%d] estaba en la cache.\n", numBloque);
					}
					encontre = 1;
					*contenido = (char*) malloc(TAMANIO_BLOQUE);
					memcpy(*contenido, aux->lineasCache[i].contenidoBloque, TAMANIO_BLOQUE);
					aux->lineasCache[i].tiempoAlmacenamiento = time(NULL);
				} else {
					if (imprimirDebug) {
						/**/printf("El bloque [%d] no estaba en la cache.\n", numBloque);
					}
					return 0;
				}
			} else {
				aux = aux->sig;
				encontre = 0;
			}
		}
		return encontre;
	} else {
		//NO PUEDE HABER BLOQUES EN LA CACHE
		return 0;
	}
}

int8_t guardarEnCache(char* path, uint64_t numBloque, char* contenidoBloque) {
	int8_t error = 0;
	t_nodoCache* aux = pCacheFte;
	int8_t encontre = 0;
	uint16_t cantLineasCache = TAMANIO_CACHE * 1024 / (sizeof(t_lineaCache) - sizeof(uint64_t) - sizeof(time_t));
	if (cantLineasCache > 0) {
		while (aux != NULL && !encontre) {
			if (imprimirDebug) {
				/**/printf("Encontre el path [%s].\n", aux->path);
			}
			if (strcmp(path, aux->path) == 0) {
				if (imprimirDebug) {
					/**/printf("Es el que buscaba.\n");
				}
				uint16_t i;
				for (i = 0; (i < cantLineasCache) && (aux->lineasCache[i].numBloque != numBloque); i++)
					;
				if ((i < cantLineasCache) && (aux->lineasCache[i].numBloque == numBloque)) {
					if (imprimirDebug) {
						/**/printf("Este bloque ya estaba en la cache, lo sobreescribo.\n");
					}
					encontre = 1;

					uint32_t j;
					for (j = 0; j < TAMANIO_BLOQUE; j++) {
						aux->lineasCache[i].contenidoBloque[j] = contenidoBloque[j];
					}
					aux->lineasCache[i].tiempoAlmacenamiento = time(NULL);
				} else {
					if (imprimirDebug) {
						/**/printf("El bloque no estaba en la cache.\n");
					}
					uint16_t lugarElegido = 0;
					int8_t lugarLibreEncontrado = 0;

					for (i = 0; i < cantLineasCache && !lugarLibreEncontrado; i++) {
						//BUSCO UN LUGAR LIBRE EN LA CACHE
						if (aux->lineasCache[i].numBloque == 0) {
							lugarElegido = i;
							if (imprimirDebug) {
								printf("El lugar [%d] esta libre.\n", i);
							}
							lugarLibreEncontrado = 1;
						}
					}

					if (!lugarLibreEncontrado) {
						//SI NO HAY UN LUGAR LIBRE EN LA CACHE BUSCO EL MAS ANTIGUO
						for (i = 0; i < cantLineasCache; i++) {
							double diferencia = difftime(aux->lineasCache[i].tiempoAlmacenamiento, aux->lineasCache[lugarElegido].tiempoAlmacenamiento);
							if (imprimirDebug) {
								/**/printf("%f\n", diferencia);
							}
							if (diferencia < 0) {
								if (imprimirDebug) {
									printf("El bloque [%d] es mas antiguo que el [%d].\n", i, lugarElegido);
								}
								lugarElegido = i;
							}
						}
						if (imprimirDebug) {
							/**/printf("El lugar con menor tiempo es el [%d].\n", lugarElegido);
						}
					}
					aux->lineasCache[lugarElegido].numBloque = numBloque;
					uint32_t j;
					for (j = 0; j < TAMANIO_BLOQUE; j++) {
						aux->lineasCache[lugarElegido].contenidoBloque[j] = contenidoBloque[j];
					}
					aux->lineasCache[lugarElegido].tiempoAlmacenamiento = time(NULL);
					encontre = 1;
				}
			} else {
				if (imprimirDebug) {
					/**/printf("No es el que buscaba.\n");
				}
				aux = aux->sig;
				encontre = 0;
			}
		}
		if ((aux == NULL) && !encontre) {
			if (imprimirDebug) {
				/**/printf("Ese archivo no esta en la cache, lo agrego.\n");
			}
			t_lineaCache* lineasCache = (t_lineaCache*) malloc(cantLineasCache * sizeof(t_lineaCache));
			memset(lineasCache, 0, cantLineasCache * sizeof(t_lineaCache));
			lineasCache[0].numBloque = numBloque;

			uint32_t i;
			for (i = 0; i < TAMANIO_BLOQUE; i++) {
				lineasCache[0].contenidoBloque[i] = contenidoBloque[i];
			}
			lineasCache[0].tiempoAlmacenamiento = time(NULL);

			for (i = 1; i < cantLineasCache; i++) {
				memset(lineasCache + i, 0, sizeof(t_lineaCache));
			}
			error = apilarCache(path, lineasCache);
		}
	}
	return error;
}

int8_t eliminarCache(char* path) {
	t_nodoCache* aux = pCacheFte;
	if (aux != NULL) {
		if (strcmp(aux->path, path) == 0) {
			//LA CACHE BUSCADA ES LA PRIMERA
			pCacheFte = pCacheFte->sig;
			free(aux);
			return 0;
		} else {
			//LA CACHE BUSCADA NO ES LA PRIMERA
			t_nodoCache* auxSig = aux->sig;
			while (auxSig != NULL && strcmp(auxSig->path, path) != 0) {
				aux = auxSig;
				auxSig = auxSig->sig;
			}
			if (auxSig != NULL && strcmp(auxSig->path, path) == 0) {
				//ENCONTRE LA CACHE CORRESPONDIENTE A ESTE ARCHIVO
				aux->sig = auxSig->sig;
				free(auxSig);
				return 0;
			} else {
				//NO SE ECONTRO LA CACHE
				return -2;
			}
		}
	} else {
		//LA PILA DE CACHE ESTA VACIA
		return -1;
	}
}

int8_t vaciarCache() {
	int8_t error = 0;
	while (pCacheFte != NULL && !error) {
		error = eliminarCache(pCacheFte->path);
	}
	return error;
}
